/**
 * 
 */
package World;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

/**
 * @author kreso
 *
 */
public class Populacija {
	//Kromosomi
	private static List<Kromosom> populacija = new ArrayList<Kromosom>();
	private static String offspring1;
	private static String offspring2;

	private static Random generator = new Random();

	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) throws IOException {
		BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
		System.out.println("Input a target number: ");
		String target_str = reader.readLine();
		
		double target = Double.parseDouble(target_str);
		System.out.println("Starting life for target number " + target);
		
		//creating random population
		for(int i = 0; i < Uvjeti.POP_SIZE; i++) {
			populacija.add(GetRandomKromosom());
			
			//System.out.println(populacija.get(i).getBits());
		}
		
		int generations_count = 0;
		
		glavna_petlja:
		while(true) {
			double total_fitness = 0.0;
			
			//calculate new fitness
			for(int i = 0; i < Uvjeti.POP_SIZE; i++) {
				populacija.get(i).AssignFitness(target);
				total_fitness += populacija.get(i).getFitness();
			}
			
			System.out.println("Generation " + generations_count + ". dominant unit:");
			PrintDominantUnit();
			
			// check to see if we have found any solutions (fitness will be 999)
            for (Kromosom i : populacija) {
            	if (i.getFitness() == 999.0f) {
            		System.out.println("\nSolution found in " + generations_count + " generations!");
            		//holds decimal values of gene sequence
            		int[] dec_val = new int[(int)Uvjeti.CHROMO_LENGTH/Uvjeti.GENE_LENGTH];
            	    i.ParseBits(dec_val);

            	    //System.out.println(Arrays.toString(dec_val));
            	    
            	    //THE END
            	    break glavna_petlja;
            	}
            }
         
            // create a new population by selecting two parents at a time and creating offspring
            // by applying crossover and mutation. Do this until the desired number of offspring
            // have been created.

      		//define some temporary storage for the new population we are about to create
            List<Kromosom> new_populacija = new ArrayList<Kromosom>();
      		
            int cPop = 0;
      		//loop until we have created POP_SIZE new chromosomes
      		while (cPop < Uvjeti.POP_SIZE) {
      			  // we are going to create the new population by grabbing members of the old population
      			  // two at a time via roulette wheel selection.
      			  offspring1 = RouletteWheel(total_fitness);
      			  offspring2 = RouletteWheel(total_fitness);

      			  //add crossover dependent on the crossover rate
      			  Crossover();

      			  //now mutate dependent on the mutation rate
      			  StringBuilder offspring1B = new StringBuilder();
      			  offspring1B.append(offspring1);
      			  StringBuilder offspring2B = new StringBuilder();
    			  offspring2B.append(offspring2);
      			  Mutate(offspring1B);
      			  Mutate(offspring2B);
//      			  System.out.println(offspring1);
//     			  System.out.println(offspring1B.toString());
//      			  System.out.println(offspring2);
//      			  System.out.println(offspring2B);

      			  Kromosom offsKromosom1 = new Kromosom();
      			  Kromosom offsKromosom2 = new Kromosom();
      			  //add these offspring to the new population. (assigning zero as their fitness scores)
      			  offsKromosom1.setBits(offspring1B.toString());
      			  offsKromosom1.setFitness(0.0f);
      			  offsKromosom2.setBits(offspring2B.toString());
      			  offsKromosom2.setFitness(0.0f);
      			  new_populacija.add(offsKromosom1);
      			  cPop++;
      			  new_populacija.add(offsKromosom2);
      			  cPop++;
      		  }//end loop	  
      		
      		  //copy temp population into main population array
      		  populacija = new_populacija;
      		  //System.out.println(populacija.get(0).getBits());
      		  
      		  ++generations_count;

      		  // exit app if no solution found within the maximum allowable number
      		  // of generations
      		  if (generations_count > Uvjeti.MAX_GENERATIONS) {
      			  System.out.println("No solutions found this run!");
      			  break glavna_petlja;
      		  }
      		 
		}
	}
	
	/**
	 * Function prints dominant unit
	 */
	private static void PrintDominantUnit() {
	 	  double max_fitness = 0.0f; 
		  Kromosom dominantan = new Kromosom();
		  
		  //finding dominant alfa male unit
		  for(Kromosom i : populacija) {
			  if(i.getFitness() > max_fitness) {
				  dominantan.setBits(i.getBits());
				  dominantan.setFitness(i.getFitness());
				  max_fitness = i.getFitness();
			  }
		  }
		  
		  //parsing bits into decimal
		  int[] dec_val = new int[(int)Uvjeti.CHROMO_LENGTH/Uvjeti.GENE_LENGTH];
		  int num_elements = dominantan.ParseBits(dec_val);
		  //and printing finally
		  for(int i = 0; i < num_elements; i++){
			  PrintGeneSymbol(dec_val[i]);
		  }
		  System.out.println();
		  System.out.print("His fitness: ");
		  System.out.println(dominantan.getFitness());
		  return;
	}
	
	/**
	 * given an integer this function outputs its symbol to the screen 
	 * 
	 * @param val - integer value for interpretation
	 */
	private static void PrintGeneSymbol(int val) {
		if (val < 10 )
			System.out.print(val + " ");
		
		else {
			switch (val) {
			
			case 10:
				System.out.print("+");
				break;
			case 11:
				System.out.print("-");
				break;
			case 12:
				System.out.print("*");
				break;
			case 13:
				System.out.print("/");
				break;
			}//end switch
			
			System.out.print(" ");
		}
		return;
	}

	//	
	/**
	 * Mutates a chromosome's bits dependent on the MUTATION_RATE
	 * 
	 * @param offspring - offspring genes for mutation
	 */
	private static void Mutate(StringBuilder offspring) {
		for(int i = 0; i < offspring.length(); i++) {
			if(generator.nextDouble() < Uvjeti.MUTATION_RATE) {
				if(offspring.charAt(i) == '1')
					offspring.setCharAt(i, '0');
				else
					offspring.setCharAt(i, '1');
			}
		}
		return;
	}
	
	/**
	 * Function performes crossover with two chromosomes creating two offstrings
	 */
	private static void Crossover() {
		//dependent on the crossover rate
		if (generator.nextDouble() < Uvjeti.CROSSOVER_RATE) {
		    //create a random crossover point
		    int cross_point = (int)(generator.nextDouble() * Uvjeti.CHROMO_LENGTH);
		
		    String geni1 = offspring1.substring(0, cross_point) + offspring2.substring(cross_point);
		    String geni2 = offspring2.substring(0, cross_point) + offspring1.substring(cross_point);
		
		    offspring1 = geni1; offspring2 = geni2;				  
		  }
	}
	
	/**
	 * selects a chromosome from the population via roulette wheel selection
	 * @param total_fitness - sum of generation fitness
	 * @return - Returns genes of the chosen creature :)
	 */
	private static String RouletteWheel(double total_fitness) {
		//generate a random number between 0 & total fitness count
		double Slice = generator.nextDouble() * total_fitness;

		//go through the chromosones adding up the fitness so far
		double FitnessSoFar = 0.0f;

		for (Kromosom i : populacija)
		{
			FitnessSoFar += i.getFitness();

			//if the fitness so far > random number return the chromo at this point
			if (FitnessSoFar >= Slice)
				return i.getBits();
		}

		return "Nisam odabrao jedniku.";
	}

	/**
	 * Stvara jedan kromosom sa random genima
	 * 
	 * @return - vraca random kromosom
	 */
	private static Kromosom GetRandomKromosom() {
		Kromosom krom = new Kromosom();
		krom.setBits(GetRandomBits(Uvjeti.CHROMO_LENGTH));
		krom.setFitness(0.0);
		
		return krom;
	}

	/**
	 * @param length - duljina kromosoma (broj gena)
	 * @return Vraca random gene kromosoma
	 */
	private static String GetRandomBits(int length) {
		String bits = new String();
		
		for(int i = 0; i < length; i++) {
			double rand_val = generator.nextDouble();
			
			if(rand_val > 0.5)
				bits += "1";
			else
				bits += "0";
		}
		return bits;
	}

}
